﻿/**
 * ServiceManager.cpp
 *
 * 作用与职责（高层概览）：
 * - 驱动安装与更新：封装对 SetupAPI/NewDev API 的调用，安装/更新 com0com 的 Bus 与端口设备。
 * - 设备枚举与命名：在安装/更新后，枚举相关设备并设置/修正 FriendlyName，保证 UI 与系统显示一致。
 * - 端口号管理：分配未占用的端口号、写入 Bus 设备的 PortNum 参数，并用于生成端口对名称。
 * - 交互与容错：当出现共享冲突、注册表缺键等情况时，通过提示（askYesNoDialog）允许用户重试或继续。
 *
 * 使用说明（关键数据与流程）：
 * - 安装流程入口为 InstallService()：分配端口号 -> 安装 Bus -> 更新 Bus/端口 FriendlyName -> 同步 ComDB。
 * - TryInstallDevice()/InstallDevice()：统一的设备创建与注册流程，按需调用 UpdateDriver() 进行驱动更新。
 * - UpdateDriver()：包装 UpdateDriverForPlugAndPlayDevices，处理强制/非强制更新、常见错误与重试逻辑。
 * - UpdateFriendlyNamesBus()/SetFriendlyNamePort()：根据 PortParameters/物理对象名，生成规范的友好名称。
 */
#include "ServiceManager.h"
#include "DriverManager.h"

#include "EnumDevices.h"
#include "PortNum.h"
#include "Params.h"
#include "Utils.h"
#include "ComDb.h"
#include "InfFile.h"

#include <cfgmgr32.h>
#include <Shlwapi.h>
#include <RegStr.h>
#include <newdev.h>

// #include <QString>


/******************** CALLBACK FUCNTION *********************/

// 前向声明
static VOID SetFriendlyNamePort(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, const QString& pPhObjName);
static VOID UpdateFriendlyNameBus(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, int num);
static bool FillPortDevProperties(DeviceProperties &devProperties, int iBus, bool isA);

// 回调：将已存在的 Bus/端口设备的端口号写入忙碌掩码，用于后续分配新端口号时避重
static bool AddDeviceToBusyMask(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr /*pDevProperties*/,
    bool * /*rebootRequired*/,
    void *pParam)
{
    int i = PortNum::GetPortNum(hDevInfo, pDevInfoData);
    if (i >= 0) {
        if (!((PortBusyMask *)pParam)->addBusyPort(i)) {
            qDebug() << "AddDeviceToBusyMask(" << i << "), because"
                     << Utils::formatWinError(GetLastError());
        }
    }
    return TRUE;
}

// 回调：当枚举到端口设备时，调用 SetFriendlyNamePort 修正显示的友好名称
static bool UpdateFriendlyNamePort(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool * /*rebootRequired*/,
    void * /*pParam*/)
{
    if (pDevProperties->getDeviceId().contains(C0C_PORT_DEVICE_ID)) {
        SetFriendlyNamePort(hDevInfo, pDevInfoData, pDevProperties->getPhysicalObjectName());
        return TRUE;
    }
    // we never should be here
    return FALSE;
}

static bool UpdateFriendlyNamesBus(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool *rebootRequired,
    void *pParam)
{
    if (pDevProperties->getDeviceId().contains(C0C_BUS_DEVICE_ID)) {
        // 获取当前Bus管理的串口对序号
        int num = PortNum::GetPortNum(hDevInfo, pDevInfoData);
        // 验证序号有效性
        if (*(int *)pParam == num || *(int *)pParam == -1) {
            // 刷新Bus的FriendlyName
            UpdateFriendlyNameBus(hDevInfo, pDevInfoData, num);
            // 刷新子端口的FriendlyName
            for (int j = 0 ; j < 2 ; j++) {
                DeviceProperties devProperties;
                if (!FillPortDevProperties(devProperties, num, j == 0))
                    return FALSE;
                EnumDevices([](const QString& hwId) {
                    return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
                }, &devProperties, rebootRequired, UpdateFriendlyNamePort, NULL);
            }
        }
        return TRUE;
    }
    // we never should be here
    return FALSE;
}

// 安装阶段回调：在设备注册成功后写入 Bus 的 PortNum 参数；失败时允许用户重试或取消
static bool InstallDeviceCallBack(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool * /*rebootRequired*/,
    void *pParam)
{
    if (pDevProperties->getDeviceId().contains(C0C_BUS_DEVICE_ID)) {
        int res;
        int num = *(int *)pParam;

        do {
            res = IDCONTINUE;

            // 设置Bus设备管理的端口对号
            // 写入PortNum到Enum\ROOT\COM0COM\0001\Device Parameters下
            LONG err = PortNum::SetPortNum(hDevInfo, pDevInfoData, num);

            if (err != ERROR_SUCCESS)
                res = Utils::askCancelTryContinueDialog(
                    "Warning",
                    QString("SetPortNum(%1) failed: %2")
                        .arg(num)
                        .arg(Utils::formatWinError(GetLastError())));

        } while (res == IDTRYAGAIN);

        if (res != IDCONTINUE)
            return FALSE;

        return TRUE;
    }

    return FALSE;
}

static bool RemoveDeviceCb(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool *pRebootRequired,
    void *pParam)
{
    int i = PortNum::GetPortNum(hDevInfo, pDevInfoData);

    if (i == ((RemoveDeviceParams *)pParam)->num) {
        ((RemoveDeviceParams *)pParam)->res =
            DisableDevice(hDevInfo, pDevInfoData, pDevProperties, pRebootRequired, NULL);

        if (((RemoveDeviceParams *)pParam)->res != IDCONTINUE)
            return FALSE;

        return RemoveDevice(hDevInfo, pDevInfoData, pDevProperties, pRebootRequired);
    }

    return TRUE;
}

/******************** TOOL FUCNTION *********************/

// 分配端口号：
// - 先枚举已存在设备，构建忙碌掩码
// - 若请求端口空闲则直接使用，否则选择第一个空闲端口
static int AllocPortNum(int num)
{
    PortBusyMask busyMask;

    DeviceProperties devProperties;
    devProperties.setDeviceId(C0C_BUS_DEVICE_ID);

    if (EnumDevices(
            [&](const QString& hwId)->bool {
                return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
            },
            &devProperties,
            nullptr,
            AddDeviceToBusyMask,
            &busyMask
            ) < 0)
        return -1;

    return busyMask.isPortFree(num) ? num : busyMask.getFirstFreePort();
}

// 端口号有效性检查：
// - 用于防止生成过长的名称导致格式化失败
// - 对异常情况弹窗提示，允许取消
static bool IsValidPortNum(int num)
{
    if (num < 0)
        return FALSE;

    char buf[C0C_PORT_NAME_LEN + 1];

    if (snprintf(buf, sizeof(buf)/sizeof(buf[0]), C0C_PREF_BUS_NAME "%d", num) < 0 ||
        snprintf(buf, sizeof(buf)/sizeof(buf[0]), C0C_PREF_PORT_NAME_A "%d", num) < 0 ||
        snprintf(buf, sizeof(buf)/sizeof(buf[0]), C0C_PREF_PORT_NAME_B "%d", num) < 0)
    {
        int res = Utils::askOkCancelDialog("Warning", QString::asprintf("The port number %d is too big", num));

        if (res == IDCANCEL)
            return FALSE;
    }

    return TRUE;
}

// 为端口设备填充枚举约束：
// - 设置设备ID为端口类
// - 设置物理对象名（如 CNCA# / CNCB#），便于枚举精准匹配
static bool FillPortDevProperties(DeviceProperties &devProperties, int iBus, bool isA)
{
    QString phDevName = QString::asprintf("%s%d", isA ? C0C_PREF_DEVICE_NAME_A : C0C_PREF_DEVICE_NAME_B, iBus);
    devProperties.setDeviceId(C0C_PORT_DEVICE_ID);
    devProperties.setPhysicalObjectName(phDevName);
    return TRUE;
}

// 设置端口设备的 FriendlyName：
// - 从物理对象名解析端口号，读取/解析 PortParameters 的端口名
// - 组合并写入 "<DeviceDesc> <CNCA#|CNCB#> (<PortName>)" 形式的友好名称
static VOID SetFriendlyNamePort(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    const QString& pPhObjName)
{
    //qDebug("SetFriendlyNamePort pPhObjName=%s\n", pPhObjName);

    if (pPhObjName.isEmpty())
        return;

    QString phPortName;
    for (int j = 0 ;; j++) {
        if (j > 1)
            return;
        const char *pPref = (j ? C0C_PREF_DEVICE_NAME_B : C0C_PREF_DEVICE_NAME_A);
        int lenPref = lstrlen(pPref);
        if (lenPref >= pPhObjName.size())
            continue;
        if (!pPhObjName.startsWith(QLatin1String(pPref)))
            continue;
        int num;
        QString sufName = pPhObjName.right(pPhObjName.size() - lenPref);
#pragma push_macro("StrToInt")
#undef StrToInt
        if (!Utils::StrToInt(sufName.toStdString().c_str(), &num) || num < 0)
            continue;
#pragma pop_macro("StrToInt")
        phPortName = QString::asprintf("%s%d", j ? C0C_PREF_PORT_NAME_B : C0C_PREF_PORT_NAME_A, num);
        break;
    }

    //qDebug("SetFriendlyNamePort phPortName=%s\n", phPortName);

    PortParameters portParameters(C0C_SERVICE, phPortName);

    QString portName;
    if (portParameters.load())
        portName = portParameters.getPortName();
    else
        portName = phPortName;

    //qDebug("SetFriendlyNamePort portName=%s\n", portName);

    char friendlyNameOld[120];
    if (!SetupDiGetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_FRIENDLYNAME, NULL,
                                          (LPBYTE)friendlyNameOld, sizeof(friendlyNameOld), NULL))
    {
        snprintf(friendlyNameOld, sizeof(friendlyNameOld)/sizeof(friendlyNameOld[0]), "");
    }

    qDebug() << "\t" << phPortName << "FriendlyName=" << friendlyNameOld;

    if (DriverManager::isComPortDeviceFromPortName(portName)) {
        HKEY hKey = SetupDiOpenDevRegKey(hDevInfo, pDevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
        if (hKey == INVALID_HANDLE_VALUE)
            return;
        char portNameBuf[20] = {0};
        DWORD len = sizeof(portNameBuf);
        LONG err = RegQueryValueEx(hKey, "PortName", NULL, NULL, reinterpret_cast<BYTE*>(portNameBuf), &len);
        RegCloseKey(hKey);
        if (err != ERROR_SUCCESS)
            return;
        portName = QString::fromLatin1(portNameBuf);
    }

    char deviceDesc[80];
    if (!SetupDiGetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_DEVICEDESC, NULL,
                                          (LPBYTE)deviceDesc, sizeof(deviceDesc), NULL))
    {
        snprintf(deviceDesc, sizeof(deviceDesc)/sizeof(deviceDesc[0]),
                 "com0com - serial port emulator");
    }

    char newFriendlyName[120];
    snprintf(newFriendlyName, sizeof(newFriendlyName)/sizeof(newFriendlyName[0]), "%s (%s)", deviceDesc, portName.toStdString().c_str());
    //qDebug("SetFriendlyNamePort friendlyName=%s\n", friendlyName);
    if (lstrcmp(newFriendlyName, friendlyNameOld) != 0) {
        if (SetupDiSetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_FRIENDLYNAME,
                                             (LPBYTE)newFriendlyName, (lstrlen(newFriendlyName) + 1) * sizeof(*newFriendlyName)))
        {
            qDebug("update %s FriendlyName=\"%s\"\n", phPortName.toStdString().c_str(), newFriendlyName);
        }
    }
}

// 根据当前 Bus 的端口号，组合端口对名称并写入 Bus 设备的 FriendlyName
static VOID UpdateFriendlyNameBus(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    int num)
{
    // 获取子端口的名称
    QString portPairName[2];
    for (int j = 0 ; j < 2 ; j++) {
        QString phPortName = QString::asprintf("%s%d", j ? C0C_PREF_PORT_NAME_B : C0C_PREF_PORT_NAME_A, num);
        PortParameters portParameters(C0C_SERVICE, phPortName);
        if (portParameters.load() == ERROR_SUCCESS)
            portPairName[j] = portParameters.getPortName();
        else
            portPairName[j] = phPortName;
    }

    // 获取原有FriendlyName
    char friendlyNameOld[120];
    if (!SetupDiGetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_FRIENDLYNAME, NULL,
                                          (LPBYTE)friendlyNameOld, sizeof(friendlyNameOld), NULL))
    {
        snprintf(friendlyNameOld, sizeof(friendlyNameOld)/sizeof(friendlyNameOld[0]), "");
    }

    // 获取备注名
    char deviceDesc[80];
    if (!SetupDiGetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_DEVICEDESC, NULL,
                                          (LPBYTE)deviceDesc, sizeof(deviceDesc), NULL))
    {
        snprintf(deviceDesc, sizeof(deviceDesc)/sizeof(deviceDesc[0]),
                 "com0com - bus for serial port pair emulator");
    }

    qDebug("       " C0C_PREF_BUS_NAME "%d FriendlyName=\"%s\" Desc=\"%s\"", num, friendlyNameOld, deviceDesc);

    // 组成新的FriendlyName
    char friendlyName[120];
    snprintf(friendlyName, sizeof(friendlyName)/sizeof(friendlyName[0]),
             "%s %d (%s <-> %s)",
             deviceDesc, num, portPairName[0].toStdString().c_str(), portPairName[1].toStdString().c_str());
    if (lstrcmp(friendlyName, friendlyNameOld) != 0) {
        // 写入注册表键值中
        if (SetupDiSetDeviceRegistryProperty(hDevInfo, pDevInfoData, SPDRP_FRIENDLYNAME,
                                             (LPBYTE)friendlyName, (lstrlen(friendlyName) + 1) * sizeof(*friendlyName)))
        {
            qDebug("update " C0C_PREF_BUS_NAME "%d FriendlyName=\"%s\"\n", num, friendlyName);
        }
    }
}

static bool UpdateRebootRequired(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, bool *pRebootRequired)
{
    if (!pRebootRequired)
        return true;

    if (*pRebootRequired)
        return true;

    ULONG status = 0;
    ULONG problem = 0;

    *pRebootRequired =
        CM_Get_DevNode_Status(&status, &problem, pDevInfoData->DevInst, 0) == CR_SUCCESS &&
        (status & DN_NEED_RESTART) != 0;

    //if (*pRebootRequired)
    //  qDebug("Enumerated status=0x%lX problem=0x%lX\n", status, problem);

    if (*pRebootRequired)
        return true;

    SP_DEVINSTALL_PARAMS installParams;

    memset(&installParams, 0, sizeof(installParams));
    installParams.cbSize = sizeof(SP_DEVINSTALL_PARAMS);

    if (!SetupDiGetDeviceInstallParams(hDevInfo, pDevInfoData, &installParams)) {
        const QString errMsg = Utils::formatWinError(GetLastError());
        qDebug() << QObject::tr("Error: SetupDiGetDeviceInstallParams(), Because %1")
                        .arg(errMsg);
        return false;
    }

    *pRebootRequired = (installParams.Flags & (DI_NEEDREBOOT|DI_NEEDRESTART)) ? true : false;

    //if (*pRebootRequired)
    //  qDebug("Enumerated Flags=0x%lX\n", installParams.Flags);

    return true;
}

static bool IsDisabled(PSP_DEVINFO_DATA pDevInfoData)
{
    ULONG status = 0;
    ULONG problem = 0;

    if (CM_Get_DevNode_Status(&status, &problem, pDevInfoData->DevInst, 0) != CR_SUCCESS)
        return FALSE;

    return (status & DN_HAS_PROBLEM) != 0 && problem == CM_PROB_DISABLED;
}

static bool ChangeState(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DWORD stateChange)
{
    SP_PROPCHANGE_PARAMS propChangeParams;

    memset(&propChangeParams, 0, sizeof(propChangeParams));
    propChangeParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    propChangeParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    propChangeParams.StateChange = stateChange;
    propChangeParams.Scope = DICS_FLAG_CONFIGSPECIFIC;
    propChangeParams.HwProfile = 0;

    if (!SetupDiSetClassInstallParams(hDevInfo, pDevInfoData, (SP_CLASSINSTALL_HEADER *)&propChangeParams, sizeof(propChangeParams))) {
        const QString errMsg = Utils::formatWinError(GetLastError());
        qDebug() << QObject::tr("Error: SetupDiSetClassInstallParams(), Because %1")
                        .arg(errMsg);
        return FALSE;
    }

    if (!SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hDevInfo, pDevInfoData)) {
        const QString errMsg = Utils::formatWinError(GetLastError());
        qDebug() << QObject::tr("Error: SetupDiCallClassInstaller(DIF_PROPERTYCHANGE), Because %3")
                        .arg(errMsg);
        return FALSE;
    }

    return TRUE;
}

int DisableDevice(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool *pRebootRequired,
    SimpleStack *pDevPropertiesStack)
{
    if (IsDisabled(pDevInfoData))
        return IDCONTINUE;

    bool rebootRequired = FALSE;

    if (!UpdateRebootRequired(hDevInfo, pDevInfoData, &rebootRequired))
        return IDCANCEL;

    //if (rebootRequired && pRebootRequired)
    //  *pRebootRequired = TRUE;

    if (!ChangeState(hDevInfo, pDevInfoData, DICS_DISABLE))
        return IDCANCEL;

    qDebug() << QObject::tr("Disabled %1 %2 %3")
                    .arg(pDevProperties->getLocation(), pDevProperties->getDeviceId(), pDevProperties->getPhysicalObjectName());

    if (!rebootRequired) {
        if (!UpdateRebootRequired(hDevInfo, pDevInfoData, &rebootRequired))
            return IDCANCEL;

        if (rebootRequired) {
            qDebug() << QObject::tr("Can't stop device %1 %2 %3")
            .arg(pDevProperties->getLocation(), pDevProperties->getDeviceId(), pDevProperties->getPhysicalObjectName());

            int res;

            res = Utils::askCancelTryContinueDialog("Error",
                                                    QObject::tr("Can't stop device %1 %2 %3.\n"
                                                                "Close application that use this device and Try Again.\n"
                                                                "Or Continue and then reboot system.\n")
                                                        .arg(pDevProperties->getLocation(),
                                                             pDevProperties->getDeviceId(),
                                                             pDevProperties->getPhysicalObjectName()),
                                                    nullptr);

            if (res == 0)
                res = IDCONTINUE;

            if (res != IDCONTINUE) {
                if (!ChangeState(hDevInfo, pDevInfoData, DICS_ENABLE))
                    return IDCANCEL;

                qDebug() << QObject::tr("Enabled %1 %2 %3")
                                .arg(pDevProperties->getLocation(), pDevProperties->getDeviceId(), pDevProperties->getPhysicalObjectName());

                return res;
            }

            if (pRebootRequired)
                *pRebootRequired = TRUE;
        }
    }

    if (pDevPropertiesStack) {
        DeviceProperties *pDevProp = new DeviceProperties(*pDevProperties);

        if (pDevProp) {
            StackElement *pElem = new StackElement(pDevProp);

            if (pElem)
                pDevPropertiesStack->push(pElem);
            else
                delete pDevProp;
        }
    }

    return IDCONTINUE;
}

bool RemoveDevice(
    HDEVINFO hDevInfo,
    PSP_DEVINFO_DATA pDevInfoData,
    ConstDevicePropertiesPtr pDevProperties,
    bool *pRebootRequired)
{
    if (!SetupDiCallClassInstaller(DIF_REMOVE, hDevInfo, pDevInfoData)) {
        const QString errMsg = Utils::formatWinError(GetLastError());
        qDebug() << QObject::tr("Error: SetupDiCallClassInstaller(DIF_REMOVE, %1, %2), Because %3")
                        .arg(pDevProperties->getDeviceId(), pDevProperties->getPhysicalObjectName(), errMsg);
        return FALSE;
    }

    qDebug() << QObject::tr("Removed %1 %2 %3")
                    .arg(pDevProperties->getLocation(), pDevProperties->getDeviceId(), pDevProperties->getPhysicalObjectName());

    return UpdateRebootRequired(hDevInfo, pDevInfoData, pRebootRequired);
}

static int RemoveDevice(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DeviceParams *pDevParams)
{
    if (!RemoveDevice(hDevInfo, pDevInfoData, &pDevParams->deviceProperties, pDevParams->enumParams->rebootRequired))
        return IDCANCEL;

    pDevParams->enumParams->deviceCount++;

    return IDCONTINUE;
}

/**
 * @brief 清理注册表中的设备枚举项
 * @param portName 端口名称（如CNCA1, CNCB1等）
 * @return 是否成功清理
 */
static bool CleanupRegistryEnumEntry(const QString& portName)
{
    // 构建注册表路径：SYSTEM\CurrentControlSet\Enum\com0com\port\{portName}
    QString registryPath = QString("SYSTEM\\CurrentControlSet\\Enum\\%1\\port\\").arg(C0C_SERVICE) + portName;

    HKEY hKey = NULL;
    LONG result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
                                registryPath.toLocal8Bit().constData(),
                                0,
                                KEY_ALL_ACCESS,
                                &hKey);

    if (result == ERROR_FILE_NOT_FOUND) {
        // 注册表项不存在，认为已经清理完成
        qDebug() << "Not Need Clean, Because Register Key Not Found";
        return true;
    }

    if (result != ERROR_SUCCESS) {
        qDebug() << "Failed to open registry key:" << registryPath
                 << "Error:" << Utils::formatWinError(result);
        return false;
    }

    RegCloseKey(hKey);

    // 删除整个注册表项及其子项
    result = RegDeleteTreeA(HKEY_LOCAL_MACHINE, registryPath.toLocal8Bit().constData());

    if (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) {
        qDebug() << "Successfully cleaned up registry entry:" << registryPath;
        return true;
    } else {
        qDebug() << "Failed to delete registry entry:" << registryPath
                 << "Error:" << Utils::formatWinError(result);
        return false;
    }
}

void PromptReboot()
{
    qWarning(">>> Reboot required. <<<");

    SetupPromptReboot(NULL, NULL, FALSE);
}

/******************** CONTROL FUCNTION *********************/

// 更新（或安装）驱动：
// - 调用 UpdateDriverForPlugAndPlayDevices
// - 处理共享冲突重试、非强制模式下的无更新场景（ERROR_NO_MORE_ITEMS）、注册表缺键提示
// - 返回 IDCONTINUE/IDTRYAGAIN/IDCANCEL 与用户交互结果保持一致
int UpdateDriver(
    const QString& pInfFilePath,
    const QString& pHardwareId,
    DWORD flags,
    bool mandatory,
    bool *pRebootRequired)
{
    DWORD updateErr = ERROR_SUCCESS;
    // 由于winApi中BOOL为int(4Bytes),而bool为1Byte,故先使用BOOL传参API
    BOOL winRebootRequired = FALSE;

    for (int i = 0 ; i < 10 ; i++) {
        if (UpdateDriverForPlugAndPlayDevices(0,
                                              pHardwareId.toStdString().c_str(),
                                              pInfFilePath.toStdString().c_str(),
                                              flags,
                                              &winRebootRequired))
        {
            updateErr = ERROR_SUCCESS;
        } else {
            updateErr = GetLastError();

            if (updateErr == ERROR_SHARING_VIOLATION) {
                qDebug(".");
                Sleep(1000);
                continue;
            }
            else
                if (!mandatory) {
                    if (updateErr == ERROR_NO_SUCH_DEVINST) {
                        updateErr = ERROR_SUCCESS;
                    }
                    else
                        if (updateErr == ERROR_NO_MORE_ITEMS && (flags & INSTALLFLAG_FORCE) == 0) {
                            updateErr = ERROR_SUCCESS;
                        }
                }

            // 在强制安装模式下，将 0x109 / ERROR_NO_MORE_ITEMS 视为可继续
            // if ((flags & INSTALLFLAG_FORCE) != 0) {
            //     if (updateErr == ERROR_NO_MORE_ITEMS || updateErr == 0x109) {
            //         updateErr = ERROR_SUCCESS;
            //     }
            // }
        }

        if (i)
            qDebug("\n");

        break;
    }

    if (pRebootRequired) {
        // BOOL转bool
        *pRebootRequired = (winRebootRequired != FALSE);
    }

    if (updateErr != ERROR_SUCCESS) {
        if (updateErr == ERROR_FILE_NOT_FOUND) {
            LONG err;
            HKEY hKey;

            err = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE, 0, KEY_READ, &hKey);

            if (err == ERROR_SUCCESS)
                RegCloseKey(hKey);

            if (err == ERROR_FILE_NOT_FOUND) {
                bool yes = Utils::askYesNoDialog("Warning",
                                                 "Can't update driver. Possible it's because your Windows registry is corrupted and\n"
                                                 "there is not the following key:\n"
                                                 "\n"
                                                 "HKEY_LOCAL_MACHINE\\" REGSTR_PATH_RUNONCE "\n"
                                                 "\n"
                                                 "Continue to add the key to the registry.\n");
                if (!yes)
                    return IDCANCEL;

                if (yes) {
                    err = RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL);

                    if (err == ERROR_SUCCESS) {
                        RegCloseKey(hKey);
                        return IDTRYAGAIN;
                    } else {
                        qDebug() << "RegCreateKeyEx() Because" << GetLastError();
                        return IDCANCEL;
                    }
                }

                return IDCANCEL;
            }
        }

        // 显示可读的错误信息
        const QString errMsg = Utils::formatWinError(updateErr);
        qDebug() << QObject::tr("UpdateDriverForPlugAndPlayDevices(\"%1\", \"%2\", 0x%3) because %4")
                        .arg(pHardwareId)
                        .arg(pInfFilePath)
                        .arg((long)flags, 0, 16)
                        .arg(errMsg);
        return Utils::askYesNoDialog("Warning",
                                     QObject::tr("UpdateDriverForPlugAndPlayDevices(\"%1\", \"%2\", 0x%3) because %4")
                                         .arg(pHardwareId)
                                         .arg(pInfFilePath)
                                         .arg((long)flags, 0, 16)
                                         .arg(errMsg))
                   ? IDCONTINUE : IDCANCEL;
    }

    return IDCONTINUE;
}

// 设备安装内部流程：
// - 创建设备信息列表并生成设备信息项
// - 写入 SPDRP_HARDWAREID 并注册设备
// - 执行安装回调（如写 PortNum）与可选的驱动更新
// - 若失败则执行 DIF_REMOVE 清理
static int TryInstallDevice(
    const QString& pInfFilePath,
    const QString& pDevId,
    const QString& pDevInstID,
    DeviceCallback pDevCallBack,
    void *pDevCallBackParam,
    bool updateDriver,
    bool *pRebootRequired)
{
    GUID classGUID;
    char className[32];

    // 读取Inf的ClassGUID与ClassName
    if (!SetupDiGetINFClass(pInfFilePath.toStdString().c_str(), &classGUID, className, sizeof(className)/sizeof(className[0]), 0)) {
        qDebug() << "SetupDiGetINFClass() failed" << pInfFilePath << "Because" << GetLastError();;
        return IDCANCEL;
    }

    // qDebug("GUID: {%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
    //        classGUID.Data1, classGUID.Data2, classGUID.Data3,
    //        classGUID.Data4[0], classGUID.Data4[1], classGUID.Data4[2], classGUID.Data4[3],
    //        classGUID.Data4[4], classGUID.Data4[5], classGUID.Data4[6], classGUID.Data4[7]);

    //qDebug("className=%s\n", className);

    // 创建空设备信息集
    HDEVINFO hDevInfo;
    hDevInfo = SetupDiCreateDeviceInfoList(&classGUID, 0);
    if (hDevInfo == INVALID_HANDLE_VALUE) {
        qDebug() << "SetupDiCreateDeviceInfoList() failed" << "Because" << GetLastError();
        return IDCANCEL;
    }

    int res = IDCONTINUE;
    SP_DEVINFO_DATA devInfoData;
    devInfoData.cbSize = sizeof(devInfoData);

    // 在Root枚举器下创建设备
    // SYSTEM\CurrentControlSet\Enum\ROOT\COM0COM\XXXX
    // root\<enumerator-specific-device-ID>
    if (pDevInstID.isEmpty()) {
        if (StrCmpNI(pDevId.toStdString().c_str(), "root\\", 5) == 0) {
            QString idSuffix = pDevId.right(pDevId.size() - 5);
            // 创建的实例ID由系统生成
            // WARNING: This API Need Administrator User Permission
            if (!SetupDiCreateDeviceInfo(hDevInfo, idSuffix.toStdString().c_str(), &classGUID, NULL, 0, DICD_GENERATE_ID, &devInfoData))
                res = IDCANCEL;
        } else {
            SetLastError(ERROR_INVALID_DEVINST_NAME);
            res = IDCANCEL;
        }
    }
    else
        // 按完整实例路径创建设备
        // <enumerator>\<enumerator-specific-device-ID>\<instance-specific-ID>
        if (StrChr(pDevInstID.toStdString().c_str(), '\\')) {
            // 含反斜杠则视为完整实例路径
            if (!SetupDiCreateDeviceInfo(hDevInfo, pDevInstID.toStdString().c_str(), &classGUID, NULL, 0, 0, &devInfoData))
                res = IDCANCEL;

            if (res != IDCONTINUE && GetLastError() == ERROR_DEVINST_ALREADY_EXISTS) {
                // Qt-style parsing: ROOT\\<enumerator>\\<instance-specific-ID>
                const QStringList tokens = pDevInstID.split('\\', QString::SkipEmptyParts);
                if (!tokens.isEmpty() && tokens.first().compare(QString::fromLatin1(REGSTR_KEY_ROOTENUM), Qt::CaseInsensitive) == 0) {
                    const QString enumerator = tokens.size() >= 2 ? tokens.at(1) : QString();
                    if (!enumerator.isEmpty()) {
                        if (SetupDiCreateDeviceInfo(hDevInfo, enumerator.toStdString().c_str(), &classGUID, NULL, 0, DICD_GENERATE_ID, &devInfoData))
                            res = IDCONTINUE;
                    }
                }
            }
        } else {
            // 不含反斜杠则视为设备ID,由系统生成实例ID
            // <enumerator-specific-device-ID>
            if (!SetupDiCreateDeviceInfo(hDevInfo, pDevInstID.toStdString().c_str(), &classGUID, NULL, 0, DICD_GENERATE_ID, &devInfoData))
                res = IDCANCEL;
        }

    if (res != IDCONTINUE) {
        qDebug("SetupDiCreateDeviceInfo() Because", GetLastError());
        goto exit1;
    }

    char hardwareId[MAX_DEVICE_ID_LEN + 1 + 1];
    snprintf(hardwareId, sizeof(hardwareId)/sizeof(hardwareId[0]) - 1, "%s", pDevId.toStdString().c_str());

    int hardwareIdLen;
    hardwareIdLen = lstrlen(hardwareId) + 1 + 1;
    hardwareId[hardwareIdLen - 1] = 0;

    // 设置硬件ID到实例
    // 创建Enum\ROOT\COM0COM\0001\下的HardwareID
    if (!SetupDiSetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_HARDWAREID,
                                          (LPBYTE)hardwareId, hardwareIdLen * sizeof(hardwareId[0])))
    {
        res = IDCANCEL;
        qDebug() << "SetupDiSetDeviceRegistryProperty()";
        goto exit1;
    }

    // 注册设备节点
    if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, hDevInfo, &devInfoData)) {
        res = IDCANCEL;
        qDebug() << "SetupDiCallClassInstaller()";
        goto exit1;
    }

    // 设置Bus管理的端口对号
    if (pDevCallBack) {
        DeviceProperties devProperties;
        devProperties.setDeviceId(pDevId);
        if (!pDevCallBack(hDevInfo, &devInfoData, &devProperties, pRebootRequired, pDevCallBackParam)) {
            res = IDCANCEL;
            goto exit2;
        }
    }

    // 更新设备
    if (updateDriver)
        res = UpdateDriver(pInfFilePath, pDevId, 0, true, pRebootRequired);

exit2:
    if (res != IDCONTINUE) {
        if (!SetupDiCallClassInstaller(DIF_REMOVE, hDevInfo, &devInfoData))
            qDebug() << "SetupDiCallClassInstaller()";
    }

exit1:
    SetupDiDestroyDeviceInfoList(hDevInfo);
    return res;
}

// 包装安装流程，处理 IDTRYAGAIN 重试语义；成功返回 true
bool InstallBusDevice(
    const QString& pInfFilePath,
    const QString& pDevId,
    const QString& pDevInstID,
    DeviceCallback pDevCallBack,
    void *pDevCallBackParam,
    bool updateDriver,
    bool *pRebootRequired)
{
    int res;

    do {
        res = TryInstallDevice(pInfFilePath, pDevId, pDevInstID, pDevCallBack, pDevCallBackParam, updateDriver, pRebootRequired);
    } while (res == IDTRYAGAIN);

    return res == IDCONTINUE;
}

static bool InstallBusDevice(const QString& pInfFilePath, int num)
{
    bool rebootRequired = false;

    // 注册 Bus 并写入 PortNum
    if (!InstallBusDevice(pInfFilePath, C0C_BUS_DEVICE_ID, NULL, InstallDeviceCallBack, &num, true, &rebootRequired))
        return FALSE;

    // 枚举并刷新所有 Bus 与子端口的 FriendlyName
    DeviceProperties devProperties;
    devProperties.setDeviceId(C0C_BUS_DEVICE_ID);
    int refreshTarget = -1; // -1 表示更新所有 Bus 实例
    EnumDevices(
        [](const QString& hwId) {
            return hwId.contains(C0C_BUS_DEVICE_ID);
        },
        &devProperties,
        &rebootRequired,
        UpdateFriendlyNamesBus,
        &refreshTarget);

    if (rebootRequired)
        PromptReboot();

    return TRUE;
}

bool InstallDriver(const QString &infFilePath, const QString &paramsA, const QString &paramsB, int num)
{
    int i, res;
    QString portPairName[2];

    // 获取当前可用串口对序号
    do {
        res = IDCONTINUE;
        i = AllocPortNum(num >= 0 ? num : 0);
        if (i < 0) {
            goto err;
        }
        if (num >= 0 && num != i) {
            QString text = QString::asprintf("The identifiers " C0C_PREF_PORT_NAME_A "%d and "
                                             C0C_PREF_PORT_NAME_B "%d are already used for other ports\n"
                                             "so ports with identifiers " C0C_PREF_PORT_NAME_A "%d and "
                                             C0C_PREF_PORT_NAME_B "%d will be installed instead.\n",
                                             num, num, i, i);
            res = Utils::askCancelTryContinueDialog("Warning", text);
        }
    } while(res == IDTRYAGAIN);
    if (res != IDCONTINUE)
        goto err;

    // 检查端口序号有效性
    if (!IsValidPortNum(i))
        goto err;

    // 解析并保存端口参数
    // 在SYSTEM\CurrentControlSet\Services\com0com\Parameters中创建端口名项
    for (int j = 0; j < 2; ++j) {
        // 生成各串口名称
        const QString& pParams = j ? paramsB : paramsA;
        const QString portName = QString("%1%2").arg(j ? C0C_PREF_PORT_NAME_B : C0C_PREF_PORT_NAME_A).arg(i);
        // 使用PortParameters加载串口信息
        PortParameters portParams(C0C_SERVICE, portName);
        if (portParams.load()) {
            // 解析传入参数
            if (!portParams.parseParametersString(pParams)) {
                goto err;
            }
            portPairName[j] = portParams.getPortName();
            if (!DriverManager::isValidPortName(portPairName[j])) {
                goto err;
            }
            portParams.initRealPortName();
            // 若有参数被改变则保存更改
            if (portParams.hasChanged()) {
                if (!portParams.save()) {
                    qDebug() << "Error: portParameters.Save(" << portName << ") because"
                             << Utils::formatWinError(GetLastError());
                }
            }
        } else {
            Utils::askYesNoDialog("Warning",
                                  QString("portParameters.Load ") + portName + " because " + Utils::formatWinError(GetLastError()));
            portPairName[j] = portName;
        }

        qDebug() << "\t" << portName << portParams.getParametersString();
    }

    if ((portPairName[0] == portPairName[1])
        && !DriverManager::isComPortDeviceFromPortName(portPairName[0])) {
        if (Utils::askYesNoDialog(
                "Warning", QString("The same port name %1 is used for both ports.\n").arg(portPairName[0])
                )
            != true) {
            goto err;
        }
    }

    // 安装Bus总线设备,其控制的串口对序号为i
    if (!InstallBusDevice(infFilePath, i)) {
        goto err;
    }

    // 同步COM端口占用状态到数据库
    ComDbSync([](const QString& hwId) {
        return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
    });
    return true;

err:
    qDebug("Error: Install not completed!");
    return FALSE;
}

bool RemoveDevices(
    DeviceEnumFilter pFilter,
    ConstDevicePropertiesPtr pDevProperties,
    bool *pRebootRequired)
{
    DeviceEnumParams enumParams;

    int res;

    enumParams.rebootRequired = pRebootRequired;
    enumParams.targetProperties = pDevProperties;

    do {
        res = EnumDevices(pFilter, (DWORD)0,
                          [](HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DeviceParams *pDevParams) -> int {
                              return RemoveDevice(hDevInfo, pDevInfoData, pDevParams);
                          },
                          &enumParams);
    } while (res == IDTRYAGAIN);

    if (res != IDCONTINUE)
        return FALSE;

    if (!enumParams.deviceCount)
        qDebug("No devices found");

    return TRUE;
}

bool UninstallDriver(int num)
{
    int res;
    bool rebootRequired = false;

    do {
        RemoveDeviceParams removeDevParams(num);
        DeviceProperties devProps;
        devProps.setDeviceId(C0C_BUS_DEVICE_ID);
        EnumDevices([](const QString& hwId) {
            return !hwId.compare(C0C_BUS_DEVICE_ID) || !hwId.compare(C0C_PORT_DEVICE_ID);
        }, &devProps, &rebootRequired, RemoveDeviceCb, &removeDevParams);
        res = removeDevParams.res;
    } while(res == IDTRYAGAIN);

    if (res == IDCONTINUE) {
        for (int j = 0; j < 2; ++j) {
            QString portName = QString("%1%2").arg(j ? C0C_PREF_PORT_NAME_B : C0C_PREF_PORT_NAME_A).arg(num);
            DeviceProperties devProps;
            devProps.setDeviceId(C0C_PORT_DEVICE_ID);
            devProps.setLocation(portName);
            RemoveDevices([](const QString& hwId) {
                return !hwId.compare(C0C_BUS_DEVICE_ID) || !hwId.compare(C0C_PORT_DEVICE_ID);
            }, &devProps, nullptr);
            
            // 清理注册表中的设备枚举项
            if (!CleanupRegistryEnumEntry(portName)) {
                qDebug() << "Warning: Failed to cleanup registry entry for port:" << portName;
            }
        }
    }

    ComDbSync([](const QString& hwId) {
        return !hwId.compare(C0C_BUS_DEVICE_ID) || !hwId.compare(C0C_PORT_DEVICE_ID);
    });

    if (rebootRequired)
        PromptReboot();

    return res == IDCONTINUE;
}

bool PreInstallService(const InfFileInstall *pInfFileInstallList)
{
    for (const InfFileInstall *infInstall = pInfFileInstallList;
         infInstall->pInfName != nullptr;
         ++infInstall
         ) {
        InfFile infFile(infInstall->pInfName, C0C_SYS_DIR);
        if (infInstall->preinstallClass) {
            int res;
            do {
                res = IDCONTINUE;
                // 安装指定的 INF 文件的 ClassInstall32 节
                if (SetupDiInstallClass(nullptr, infFile.Path(), DI_QUIETINSTALL, nullptr)) {
                    qDebug("Installed Class %s\n", infFile.ClassGUID());
                } else {
                    const QString errMsg = Utils::formatWinError(GetLastError());
                    qDebug() << QObject::tr("PreInstallSevice Error: SetupDiInstallClass() because %1")
                                    .arg(errMsg);
                }
            } while(res == IDTRYAGAIN);

            if (res != IDCONTINUE)
                goto err;
        }
        if (!infFile.InstallOEMInf())
            goto err;
    }
    return true;
err:
    qWarning("\nPreinstall not completed!\n");
    return false;
}

static bool IsEnabled(PSP_DEVINFO_DATA pDevInfoData)
{
    ULONG status = 0;
    ULONG problem = 0;

    if (CM_Get_DevNode_Status(&status, &problem, pDevInfoData->DevInst, 0) != CR_SUCCESS)
        return FALSE;

    return (status & (DN_HAS_PROBLEM|DN_NEED_RESTART)) == 0;
}

static int EnableDevice(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DeviceParams *pDevParams)
{
    if (IsEnabled(pDevInfoData))
        return IDCONTINUE;

    if (ChangeState(hDevInfo, pDevInfoData, DICS_ENABLE)) {
        qDebug() << QString("Enabled %s %s %s\n").arg(pDevParams->deviceProperties.getLocation(),
                                                      pDevParams->deviceProperties.getDeviceId(),
                                                      pDevParams->deviceProperties.getPhysicalObjectName());
    }

    UpdateRebootRequired(hDevInfo, pDevInfoData, pDevParams->enumParams->rebootRequired);

    return IDCONTINUE;
}

bool EnableDevices(
    DeviceEnumFilter pFilter,
    ConstDevicePropertiesPtr pDevProperties,
    bool *pRebootRequired)
{
    DeviceEnumParams enumParams;

    int res;

    enumParams.rebootRequired = pRebootRequired;
    enumParams.targetProperties = pDevProperties;

    do {
        res = EnumDevices(pFilter, DIGCF_PRESENT, EnableDevice, &enumParams);
    } while (res == IDTRYAGAIN);

    if (res != IDCONTINUE)
        return FALSE;

    return TRUE;
}

static VOID CleanDevPropertiesStack(
    SimpleStack &stack,
    bool enable,
    bool *pRebootRequired)
{
    for (;;) {
        StackElement *pElem = stack.pop();
        if (!pElem)
            break;
        DeviceProperties *pDevProperties = (DeviceProperties *)pElem->data;
        delete pElem;

        if (pDevProperties) {
            if (enable && !pDevProperties->getPhysicalObjectName().isEmpty())
                EnableDevices([](const QString& hwId) {
                    return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
                }, pDevProperties, pRebootRequired);
            delete pDevProperties;
        }
    }
}

bool UninstallService(const InfFileInstall *pInfFileInstallList, const InfFile::InfFileUninstall *pInfFileUninstallList)
{
    bool rebootRequired = FALSE;

    DeviceProperties devProperties;
    devProperties = DeviceProperties();
    devProperties.setDeviceId(C0C_PORT_DEVICE_ID);

    {
        SimpleStack stack;

        if (!DisableDevices([](const QString& hwId) {
                return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
            }, &devProperties, &rebootRequired, &stack)) {
            CleanDevPropertiesStack(stack, TRUE, &rebootRequired);
            goto err;
        }

        CleanDevPropertiesStack(stack, FALSE, &rebootRequired);
    }

    devProperties = DeviceProperties();
    devProperties.setDeviceId(C0C_BUS_DEVICE_ID);

    if (!RemoveDevices([](const QString& hwId) {
            return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
        }, &devProperties, &rebootRequired))
        goto err;

    if (!RemoveDevices([](const QString& hwId) {
            return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
        }, NULL, NULL))
        goto err;

    ComDbSync([](const QString& hwId) {
        return hwId.contains(C0C_BUS_DEVICE_ID) || hwId.contains(C0C_PORT_DEVICE_ID);
    });

    if (rebootRequired) {
        PromptReboot();
        goto err;
    }

    int res;
    bool notDeleted;
    LONG err;

    do {
        notDeleted = TRUE;
        res = IDCONTINUE;

        SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

        if (hSCM) {
            do {
                res = IDCONTINUE;

                SC_HANDLE hSrv = OpenService(hSCM, C0C_SERVICE, DELETE|SERVICE_QUERY_STATUS);

                if (hSrv) {
                    SERVICE_STATUS srvStatus;

                    if (QueryServiceStatus(hSrv, &srvStatus)) {
                        if (srvStatus.dwCurrentState == SERVICE_STOPPED) {
                            bool yes = Utils::askYesNoDialog(
                                QStringLiteral("Warning"),
                                QString(
                                    "The deleting %1 service will remove your manual settings.\n"
                                    "Would you like to delete service?\n"
                                    ).arg(QString::fromLatin1(C0C_SERVICE))
                                );
                            if (yes)
                            {
                                if (DeleteService(hSrv)) {
                                    qDebug("Deleted Service %s\n", C0C_SERVICE);
                                    notDeleted = FALSE;
                                } else {
                                    const QString errMsg = Utils::formatWinError(GetLastError());
                                    res = Utils::askCancelTryContinueDialog(
                                        QStringLiteral("Error"),
                                        QString("DeleteService(%1) failed: %2")
                                            .arg(QString::fromLatin1(C0C_SERVICE))
                                            .arg(errMsg)
                                        );
                                }
                            }
                        } else {
                            res = Utils::askCancelTryContinueDialog(
                                QStringLiteral("Warning"),
                                QString("Service %1 is not stopped (state %2).")
                                    .arg(QString::fromLatin1(C0C_SERVICE))
                                    .arg((long)srvStatus.dwCurrentState)
                                );
                        }
                    } else {
                        const QString errMsg = Utils::formatWinError(GetLastError());
                        res = Utils::askCancelTryContinueDialog(
                            QStringLiteral("Error"),
                            QString("QueryServiceStatus(%1) failed: %2")
                                .arg(QString::fromLatin1(C0C_SERVICE))
                                .arg(errMsg)
                            );
                    }

                    if (!CloseServiceHandle(hSrv)) {
                        const QString errMsg = Utils::formatWinError(GetLastError());
                        qWarning() << "CloseServiceHandle(hSrv) failed:" << errMsg;
                    }
                } else {
                    if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) {
                        qDebug("Service %s not installed\n", C0C_SERVICE);
                        notDeleted = FALSE;
                    } else {
                        const QString errMsg = Utils::formatWinError(GetLastError());
                        res = Utils::askCancelTryContinueDialog(
                            QStringLiteral("Error"),
                            QString("OpenService(%1) failed: %2")
                                .arg(QString::fromLatin1(C0C_SERVICE))
                                .arg(errMsg)
                            );
                    }
                }
            } while (res == IDTRYAGAIN);

            if (!CloseServiceHandle(hSCM)) {
                const QString errMsg = Utils::formatWinError(GetLastError());
                qWarning() << "CloseServiceHandle(hSCM) failed:" << errMsg;
            }
        } else {
            const QString errMsg = Utils::formatWinError(GetLastError());
            res = Utils::askCancelTryContinueDialog(
                QStringLiteral("Error"),
                QString("OpenSCManager() failed: %1").arg(errMsg)
                );
        }
    } while (res == IDTRYAGAIN);

    if (notDeleted)
        qDebug("WARNING: Service %s not deleted\n", C0C_SERVICE);

    if (res != IDCONTINUE)
        goto err;

    do {
        notDeleted = TRUE;
        res = IDCONTINUE;

        err = RegDeleteKey(HKEY_LOCAL_MACHINE, C0C_REGKEY_EVENTLOG);

        if (err == ERROR_SUCCESS) {
            qDebug("Deleted EventLog %s\n", C0C_SERVICE);
            notDeleted = FALSE;
        }
        else
            if (err == ERROR_FILE_NOT_FOUND) {
                qDebug("EventLog %s not installed\n", C0C_SERVICE);
                notDeleted = FALSE;
            }
            else {
                const QString errMsg = Utils::formatWinError(err);
                res = Utils::askCancelTryContinueDialog(
                    QStringLiteral("Error"),
                    QString("RegDeleteKey(%1) failed: %2")
                        .arg(QString::fromLatin1(C0C_REGKEY_EVENTLOG))
                        .arg(errMsg)
                    );
            }
    } while (res == IDTRYAGAIN);

    if (notDeleted)
        qDebug("WARNING: Key %s not deleted\n", C0C_REGKEY_EVENTLOG);

    if (res != IDCONTINUE)
        goto err;

    do {
        notDeleted = TRUE;
        res = IDCONTINUE;

        HKEY hKey = SetupDiOpenClassRegKey(NULL, DELETE);

        if (hKey != INVALID_HANDLE_VALUE) {
            do {
                res = IDCONTINUE;

                err = RegDeleteKey(hKey, C0C_CNCCLASS_GUID);

                if (err != ERROR_SUCCESS) {
                    HKEY hClassGuidKey;
                    err = RegOpenKeyEx(hKey, C0C_CNCCLASS_GUID, 0, KEY_READ, &hClassGuidKey);

                    if (err == ERROR_SUCCESS) {
                        for (;;) {
                            char subKey[MAX_PATH + 1];
                            DWORD subKeySize = sizeof(subKey)/sizeof(subKey[0]);

                            err = RegEnumKeyEx(hClassGuidKey, 0, subKey, &subKeySize, NULL, NULL, NULL, NULL);

                            if (err != ERROR_SUCCESS)
                                break;

                            err = RegDeleteKey(hClassGuidKey, subKey);

                            if (err == ERROR_SUCCESS)
                                qDebug("Deleted Class subkey %s\\%s\n", C0C_CNCCLASS_GUID, subKey);
                            else
                                if (err != ERROR_FILE_NOT_FOUND) {
                                    const QString errMsg = Utils::formatWinError(err);
                                    qWarning() << "RegDeleteKey(" << C0C_CNCCLASS_GUID << "\\" << subKey << ") failed:" << errMsg;
                                    break;
                                }
                        }

                        err = RegCloseKey(hClassGuidKey);

                        if (err != ERROR_SUCCESS) {
                            const QString errMsg = Utils::formatWinError(err);
                            qWarning() << "RegCloseKey() failed:" << errMsg;
                        }
                    }

                    err = RegDeleteKey(hKey, C0C_CNCCLASS_GUID);
                }

                if (err == ERROR_SUCCESS) {
                    qDebug("Deleted Class %s\n", C0C_CNCCLASS_GUID);
                    notDeleted = FALSE;
                }
                else
                    if (err == ERROR_FILE_NOT_FOUND) {
                        qDebug("Class %s not installed\n", C0C_CNCCLASS_GUID);
                        notDeleted = FALSE;
                    }
                    else {
                        const QString errMsg = Utils::formatWinError(err);
                        res = Utils::askCancelTryContinueDialog(
                            QStringLiteral("Error"),
                            QString("RegDeleteKey(%1) failed: %2")
                                .arg(QString::fromLatin1(C0C_CNCCLASS_GUID))
                                .arg(errMsg)
                            );
                    }
            } while (res == IDTRYAGAIN);

            err = RegCloseKey(hKey);

            if (err != ERROR_SUCCESS) {
                const QString errMsg = Utils::formatWinError(err);
                qWarning() << "RegCloseKey() failed:" << errMsg;
            }
        } else {
            const QString errMsg = Utils::formatWinError(GetLastError());
            res = Utils::askCancelTryContinueDialog(
                QStringLiteral("Error"),
                QString("SetupDiOpenClassRegKey(NULL) failed: %1").arg(errMsg)
                );
        }
    } while (res == IDTRYAGAIN);

    if (notDeleted)
        qDebug("WARNING: Class %s not deleted\n", C0C_CNCCLASS_GUID);

    if (res != IDCONTINUE)
        goto err;

    for (
        const InfFileInstall *pInfFileInstall = pInfFileInstallList ;
        pInfFileInstall->pInfName != NULL ;
        pInfFileInstall++)
    {
        InfFile infFile(pInfFileInstall->pInfName, pInfFileInstall->pInfName);

        if (!infFile.UninstallOEMInf())
            goto err;

        if (pInfFileInstall->pCopyDriversSection != NULL) {
            if (!infFile.UninstallFiles(pInfFileInstall->pCopyDriversSection))
                goto err;
        }
    }

    if (!InfFile::UninstallAllInfFiles(pInfFileUninstallList, NULL))
        goto err;

    return TRUE;

err:

    qDebug("\nUninstall not completed!\n");

    return FALSE;
}

static int DisableDevice(HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DeviceParams* pDevParams)
{
    int res = DisableDevice(hDevInfo,
                            pDevInfoData,
                            &pDevParams->deviceProperties,
                            pDevParams->enumParams->rebootRequired,
                            pDevParams->enumParams->deviceStack);

    if (res == IDCONTINUE)
        pDevParams->enumParams->deviceCount++;

    return res;
}

bool DisableDevices(DeviceEnumFilter pFilter, ConstDevicePropertiesPtr pDevProperties, bool *pRebootRequired, SimpleStack *pDevPropertiesStack)
{
    DeviceEnumParams enumParams;

    int res;

    enumParams.rebootRequired = pRebootRequired;
    enumParams.targetProperties = pDevProperties;
    enumParams.deviceStack = pDevPropertiesStack;

    do {
        res = EnumDevices(pFilter,
                          DIGCF_PRESENT,
                          [](HDEVINFO hDevInfo, PSP_DEVINFO_DATA pDevInfoData, DeviceParams *pDevParams) -> int {
                              return DisableDevice(hDevInfo, pDevInfoData, pDevParams);
                          },
                          &enumParams);
    } while (res == IDTRYAGAIN);

    if (res != IDCONTINUE)
        return FALSE;

    if (!DriverManager::waitForInstallEvents(10))
        Sleep(1000);

    return TRUE;
}
